﻿using System;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Windows.Controls;
using System.Linq;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Text;

namespace Microsoft.Popfly.GameCreator.GameEngine
{
    public class Actor : BehaviorOwner, IUpdateable
    {
        public DebugCollisionEdges DebugCollisionEdges { get; set; }

        public String ActorName { get; private set; }

        [Obsolete("Use ActorName")]
        public String name { get { return ActorName;  } }

        public String instanceName
        {
            get
            {
                return Name;
            }
        }
        
        internal bool Removed { get; private set; }

        public double PrevX { get; private set; }
        public double PrevY { get; private set; }

        public override double X { get; set; }
        public override double Y { get; set; }

        public double PrevVisualX { get; private set; }
        public double VisualX
        {
            get
            {
                return X + (m_currentState.VisualXOffset * ScaleX);
            }
            set
            {
                X = value - (m_currentState.VisualXOffset * ScaleX);
            }
        }

        public double PrevVisualY { get; private set; }
        public double VisualY
        {
            get
            {
                return Y + (m_currentState.VisualYOffset * ScaleY);
            }
            set
            {
                Y = value - (m_currentState.VisualYOffset * ScaleY);
            }

        }

        public double PrevRotation { get; private set; }
        private double m_rotation;
        public double Rotation
        {
            get
            {
                return m_rotation;
            }
            set
            {
                while (value < 0)
                {
                    value += 360.0;
                }
                m_rotation = value % 360.0;
            }
        }

        public double RotationVelocity { get; set; }
        public double RotationAcceleration { get; set; }
        private double m_rotationDrag;
        public double RotationDrag
        {
            get
            {
                return m_rotationDrag;
            }
            set
            {
                if (value > 1.0)
                {
                    m_rotationDrag = 1.0;
                    Console.ReportError("Error: Cannot set RotationDrag on '" + Name + "' to '" + value +
                        "'.  Drag properties must be between 0 and 1.0.  Setting drag to 1.0.  ");

                }
                else if (value < 0.0)
                {
                    m_rotationDrag = 0.0;
                    Console.ReportError("Error: Cannot set RotationDrag on '" + Name + "' to '" + value +
                        "'.  Drag properties must be between 0 and 1.0.  Setting drag to 0.0.  ");
                }
                else
                {
                    m_rotationDrag = value;
                }
            }
        }

        public double PrevWidth { get; private set; }
        public override double Width
        {
            get { return m_width; }
            set
            {
                ScaleX = value / m_naturalWidth;
                m_width = value;
            }
        }
        public double PrevHeight { get; private set; }
        public override double Height
        {
            get { return m_height; }
            set
            {
                ScaleY = value / m_naturalHeight;
                m_height = value;
            }
        }
        // since we need to initialize this manually, we can't use an automatically implemented 
        // property
        private double m_width;
        private double m_height;

        public double PrevVisualWidth { get; private set; }
        public double VisualWidth
        {
            get { return m_currentState.VisualWidth * ScaleX; }
        }
        public double PrevVisualHeight { get; private set; }
        public double VisualHeight
        {
            get { return m_currentState.VisualHeight * ScaleY; }
        }
        public double ScaleX
        {
            get { return m_scaleX; }
            set
            {
                m_width = value * m_naturalWidth;
                m_scaleX = value;
            }
        }
        public double ScaleY
        {
            get
            {
                return m_scaleY;
            }
            set
            {
                m_height = value * m_naturalHeight;
                m_scaleY = value;
            }
        }

        public bool MovedThisFrame
        {
            get
            {
                return (X != PrevX || Y != PrevY || Width != PrevWidth || Height != PrevHeight || Rotation != PrevRotation); 
                // we should add Rotation != PrevRotation
            }
        }

        private double m_scaleX;
        private double m_scaleY;
        public double ScaleXVelocity { get; set; }
        public double ScaleYVelocity { get; set; }
        public double ScaleXAcceleration { get; set; }
        public double ScaleYAcceleration { get; set; }     

        private double m_scaleXDrag, m_scaleYDrag;
        public double ScaleXDrag
        {
            get
            {
                return m_scaleXDrag;
            }
            set
            {
                if (value > 1.0)
                {
                    m_scaleXDrag = 1.0;
                    Console.ReportError("Error: Cannot set ScaleXDrag on '" + Name + "' to '" + value +
                        "'.  Drag properties must be between 0 and 1.0.  Setting drag to 1.0.  ");

                }
                else if (value < 0.0)
                {
                    m_scaleXDrag = 0.0;
                    Console.ReportError("Error: Cannot set ScaleXDrag on '" + Name + "' to '" + value +
                        "'.  Drag properties must be between 0 and 1.0.  Setting drag to 0.0.  ");
                }
                else
                {
                    m_scaleXDrag = value;
                }
            }
        }
        public double ScaleYDrag
        {
            get
            {
                return m_scaleYDrag;
            }
            set
            {
                if (value > 1.0)
                {
                    m_scaleYDrag = 1.0;
                    Console.ReportError("Error: Cannot set ScaleYDrag on '" + Name + "' to '" + value +
                        "'.  Drag properties must be between 0 and 1.0.  Setting drag to 1.0.  ");

                }
                else if (value < 0.0)
                {
                    m_scaleYDrag = 0.0;
                    Console.ReportError("Error: Cannot set ScaleYDrag on '" + Name + "' to '" + value +
                        "'.  Drag properties must be between 0 and 1.0.  Setting drag to 0.0.  ");
                }
                else
                {
                    m_scaleYDrag = value;
                }
            }
        }

        public double? PrevOpacity { get; private set; }
        public double Opacity { get; set; }
        public double PrevZIndex { get; set; }
        public int ZIndex { get; set; }
        public double Mass { get; set; }
        public bool IsSolid;

        private Data.ActorInfo m_actorInfo;


        private Scene m_scene;
        private Dictionary<string, State> m_states = new Dictionary<string, State>();
        private ActorCanvas m_actorCanvas = new ActorCanvas();
        private double m_naturalWidth;
        private double m_naturalHeight;

        // used during collision resolution by Scene
        internal Actor PushingActor;
        internal Dictionary<string, Actor> ActorsCollidedWith =
            new Dictionary<string, Actor>();
        internal Dictionary<string, Actor> PreviousActorsCollidedWith =
            new Dictionary<string, Actor>();

        internal Dictionary<string, Events.CollisionEventArgs> ActorsCollidedWithData =
            new Dictionary<string, Events.CollisionEventArgs>();
        internal Dictionary<string, Events.CollisionEventArgs> PreviousActorsCollidedWithData =
            new Dictionary<string, Events.CollisionEventArgs>();

        internal Actor Parent;
        internal bool InitialChildOverlap;
        internal bool ChangedStateThisFrame = false;
        public State CurrentState
        {
            get
            {
                return m_currentState;
            }
            set
            {
                if (m_currentState != value)
                {
                    ChangedStateThisFrame = true;
                }
                m_currentState = value;

                m_actorCanvas.Children.Clear();
                m_actorCanvas.Children.Add(value.StateObject);
                m_naturalWidth = value.Width;
                m_naturalHeight = value.Height;


                double oldWidth = Width;
                double oldHeight = Height;

                // calculate width and height (yes this looks weird, but setting the scaleX property calculates
                // width using m_naturalWidth)
                ScaleX = m_scaleX;
                ScaleY = m_scaleY;

                // If the new state is a different size than the previous state, center it.
                if (oldWidth > 0 && oldWidth != Width)
                    X = X + (oldWidth - Width) / 2;
                if (oldHeight > 0 && oldHeight != Height)
                    Y = Y + (oldHeight - Height) / 2;

                IsSolid = value.IsSolid;
                
                UpdateEdgesOutline();
            }
        }

        private void UpdateEdgesOutline()
        {
            if (DebugCollisionEdges != null)
                m_scene.GetCanvasRoot().Children.Remove(DebugCollisionEdges.Canvas);

            DebugCollisionEdges = DebugCollisionEdges.Create(CurrentState.Edges.Type, IsSolid);

            m_scene.GetCanvasRoot().Children.Add(DebugCollisionEdges.Canvas);
        }

        private State m_currentState;

        public State currentState
        {
            get
            {
                return CurrentState;
            }
            set
            {
                CurrentState = value;
            }
        }

        public bool ChangeState(string stateName)
        {
            if (CurrentState != null && stateName == CurrentState.Name)
            {
                return true;
            }

            if (m_states.ContainsKey(stateName))
            {
                Console.Write("MSGA001: Changing the state of " + Name + " to " + stateName);
                CurrentState = m_states[stateName];
                return true;
            }
            else
            {
                StringBuilder errorMessage = new StringBuilder("Cannot change to state '" + stateName + "' on actor '" + Name + "'.  Actor does not have a state of that name." +
                    "\tActor states:");
                foreach (var kvp in m_states)
                {
                    errorMessage.Append("\t\tstate: '" + kvp.Key + "'");
                }
                errorMessage.Append("\n");

                Console.ReportError(errorMessage.ToString());

                return false;
            }
        }

        internal void InitActorBehaviors()
        {
            InitBehaviors(m_scene, m_actorInfo.behaviors);
        }

        private void Init(Game game, Scene scene, Data.ActorInfo actorInfo, double x, double y, double rotation, double scaleX, double scaleY, Data.EffectInfo effect)
        {
            m_game = game;
            m_scene = scene;
            ActorName = actorInfo.Name;
            m_actorCanvas.Name = Name;
            m_actorInfo = actorInfo;

            // increment the # of this actor on the scene
            string numActorProp = "# of " + actorInfo.Name;
            object obj = scene.GetValue(numActorProp);
            int curNum = 0;
            if (obj != null)
            {
                curNum = Utils.IntFromObject(obj);

            }
            scene.SetValue(numActorProp, curNum + 1);

            Removed = false;

            // Init BehaviorOwner members
            IsActor = true;

            foreach (KeyValuePair<string, Data.ActorInfo.StateInfo> kvp in actorInfo.States)
            {
                m_states.Add(kvp.Key, new State(kvp.Key, kvp.Value, game.GameResources));
            }

            // Set this before we set ScaleX and ScaleY from properties
            // TODO: We should add a first scene param instead of always using scene 0
            ChangeState(actorInfo.States.getIndexedKey(0));

            foreach (KeyValuePair<string, Data.PropertyInfo> kvp in actorInfo.properties)
            {
                try
                {
                    SetValue(kvp.Key, kvp.Value.Value);
                }
                catch (Exception e)
                {
                    System.Text.StringBuilder errorMessage = new System.Text.StringBuilder(
                        "Cannot set '" + kvp.Key + "' to '" + kvp.Value.Value + "' on actor instance '" + Name +
                        "'.\n");
                    errorMessage.Append("Message: " + e.Message + "\n");
                    errorMessage.Append("Exception: " + e.ToString() + "\n");
                    Console.ReportError(errorMessage.ToString());
                }
            }

            X = x;
            Y = y;
            Rotation = rotation;
            ScaleX = scaleX;
            ScaleY = scaleY;            

            SilverlightUpdate();

            UpdateEdgesOutline();

            // Init the behaviors now if this actor was dynamically created
            // Otherwise, if it's created as part of the scene creation, 
            // the scene will handle this once all the actors have been created so
            // actor references can be resolved.
            if (scene == game.CurrentScene)
                InitBehaviors(scene, actorInfo.behaviors);

            // add in the effect
            bool hasEffect = (effect != null && (effect.Fade || effect.Scale));
            if (hasEffect)
            {
                Storyboard effectStoryboard = new Storyboard();
                if (effect.Fade)
                {
                    DoubleAnimation fadeInAnimation = new DoubleAnimation();
                    fadeInAnimation.From = 0.0;
                    fadeInAnimation.To = 1.0;
                    fadeInAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.2));
                    Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(ActorCanvas.OpacityProperty));
                    Storyboard.SetTarget(fadeInAnimation, m_actorCanvas);
                    effectStoryboard.Children.Add(fadeInAnimation);
                }
                if (effect.Scale)
                {
                    DoubleAnimation scaleXAnimation = new DoubleAnimation();
                    scaleXAnimation.From = 0.0;
                    scaleXAnimation.To = 1.0;
                    scaleXAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.2));
                    Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath(ScaleTransform.ScaleXProperty));
                    Storyboard.SetTarget(scaleXAnimation, m_actorCanvas.__Scale);


                    DoubleAnimation scaleYAnimation = new DoubleAnimation();
                    scaleYAnimation.From = 0.0;
                    scaleYAnimation.To = 1.0;
                    scaleYAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.2));
                    Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath(ScaleTransform.ScaleYProperty));
                    Storyboard.SetTarget(scaleYAnimation, m_actorCanvas.__Scale);

                    effectStoryboard.Children.Add(scaleXAnimation);
                    effectStoryboard.Children.Add(scaleYAnimation);
                }

                effectStoryboard.Begin();
            }            

            // Clear out property change events that occured as a part of init if this was dynamically created
            // (otherwise, the game will take care of it once CurrentScene has been set
            if (game.CurrentScene == scene)
                InitPropertyChangePreviousValues();
        }
       
        public Actor(Game game, Scene scene, Data.ActorInfo actorInfo, Data.ActorInstanceInfo actorInstanceInfo) : base(game, actorInstanceInfo.Name)
        {
            double VisualXOffset = -actorInfo.States.getIndexedValue(0).Edges.Left;
            double VisualYOffset = -actorInfo.States.getIndexedValue(0).Edges.Top;

            double x = actorInstanceInfo.VisualX - VisualXOffset * actorInstanceInfo.ScaleX;
            double y = actorInstanceInfo.VisualY - VisualYOffset * actorInstanceInfo.ScaleY;

            Init(game, scene, actorInfo, x, y, 0.0, actorInstanceInfo.ScaleX, actorInstanceInfo.ScaleY, null);
        }

        public Actor(Game game, Scene scene, Data.ActorInfo actorInfo, string name, double x, double y, double scaleX, double scaleY) : base(game, name)
        {
            Init(game, scene, actorInfo, x, y, 0.0, scaleX, scaleY, null);
        }

        public Actor(Game game, Scene scene, Data.ActorInfo actorInfo, string name, double x, double y, double scaleX, double scaleY, Data.EffectInfo effect)
            : base(game, name)
        {
            Init(game, scene, actorInfo, x, y, 0.0, scaleX, scaleY, effect);
        }

        public Actor(Game game, Scene scene, Data.ActorInfo actorInfo, string name, double x, double y, double rotation, double scaleX, double scaleY, Data.EffectInfo effect)
            : base(game, name)
        {
            Init(game, scene, actorInfo, x, y, rotation, scaleX, scaleY, effect);
        }

        public void Remove(Data.EffectInfo effect)
        {
            if (Removed == false) // only remove it the first time
            {
                Console.Write("MSGA002: Removing " + Name);
                bool hasEffect = (effect != null && (effect.Fade || effect.Scale));
                if (hasEffect)
                {
                    Storyboard effectStoryboard = new Storyboard();
                    if (effect.Fade)
                    {
                        DoubleAnimation fadeOut = new DoubleAnimation();
                        fadeOut.To = 0.0;
                        fadeOut.Duration = new Duration(TimeSpan.FromSeconds(0.2));
                        Storyboard.SetTargetProperty(fadeOut, new PropertyPath(ActorCanvas.OpacityProperty));
                        Storyboard.SetTarget(fadeOut, m_actorCanvas);
                        effectStoryboard.Children.Add(fadeOut);
                    }
                    if (effect.Scale)
                    {
                        DoubleAnimation scaleX = new DoubleAnimation();
                        scaleX.By = 0.25;
                        scaleX.Duration = new Duration(TimeSpan.FromSeconds(0.2));
                        Storyboard.SetTargetProperty(scaleX, new PropertyPath(ScaleTransform.ScaleXProperty));
                        Storyboard.SetTarget(scaleX, m_actorCanvas.__Scale);


                        DoubleAnimation scaleY = new DoubleAnimation();
                        scaleY.By = 0.25;
                        scaleY.Duration = new Duration(TimeSpan.FromSeconds(0.2));
                        Storyboard.SetTargetProperty(scaleY, new PropertyPath(ScaleTransform.ScaleYProperty));
                        Storyboard.SetTarget(scaleY, m_actorCanvas.__Scale);

                        effectStoryboard.Children.Add(scaleX);
                        effectStoryboard.Children.Add(scaleY);
                    }

                    effectStoryboard.Completed += new EventHandler(effectStoryboard_Completed);
                    effectStoryboard.Begin();
                }

                Removed = true;

                m_scene.QueueForRemoval(this, !hasEffect);

                // raise disappear events
                m_queueDisappearRemoves = true;
                foreach (Events.DisappearListener listener in m_disappearEventListeners)
                {
                    listener.Raise();
                }
                m_queueDisappearRemoves = false;
            }
            else
            {
                Console.IssueWarning("Attempting to remove already removed actor " + Name);
            }
        }

        private void effectStoryboard_Completed(object sender, EventArgs e)
        {
            m_scene.RemoveVisual(m_actorCanvas);
        }

        /// <summary>
        /// Updates the behaviors.  Due to compat issues with the previous jscript engine, 
        /// a lot of the code that actually needs to happen each frame does not 
        /// take place in this function.  If you aren't worried about compat, feel free to 
        /// reconsolidate, moving the rest of the functions called in Scene's update back into 
        /// this update (PhysicsUpdate, Draw etc).
        /// </summary>
        /// <param name="elapsedTimeInSeconds">The number of seconds since the last frame</param>
        public void Update(double elapsedTimeInSeconds)
        {
            ChangedStateThisFrame = false;
            BehaviorsUpdate(elapsedTimeInSeconds);
        }

        /// <summary>
        /// Update silverlight with the current positions of the actors.  When porting to other platforms, you should draw everything
        /// based on its current position here.
        /// </summary>
        public void Draw()
        {
            SilverlightUpdate();
            DebugCollisionEdges.Draw(m_game.ShowDebugCollisionEdges, GetBounds());
        }

        /// <summary>
        /// Moves the actor according to it's position, velocity, acceleration and drag
        /// </summary>
        /// <param name="elapsedTimeInSeconds">Number of seconds since the last update</param>
        internal void PhysicsUpdate(double elapsedTimeInSeconds)
        {
            double xDrag = XDrag;
            double yDrag = YDrag;
            double rotationDrag = RotationDrag;
            double scaleXDrag = ScaleXDrag;
            double scaleYDrag = ScaleYDrag;
            double xVelocity = XVelocity;
            double yVelocity = YVelocity;
            double rotationVelocity = RotationVelocity;
            double scaleXVelocity = ScaleXVelocity;
            double scaleYVelocity = ScaleYVelocity;
            double xAcceleration = XAcceleration;
            double yAcceleration = YAcceleration;
            double rotationAcceleration = RotationAcceleration;
            double scaleXAcceleration = ScaleXAcceleration;
            double scaleYAcceleration = ScaleYAcceleration;

            double updatedXVelocity = xVelocity;
            if (xAcceleration != 0 || (xDrag != 0 && xVelocity != 0))
            {
                updatedXVelocity = PhysicsUtils.GetNewVelocity(elapsedTimeInSeconds, xDrag, xVelocity, xAcceleration);
                XVelocity = updatedXVelocity;
            }

            double updatedYVelocity = yVelocity;
            if (yAcceleration != 0 || (yDrag != 0 && yVelocity != 0))
            {
                updatedYVelocity = PhysicsUtils.GetNewVelocity(elapsedTimeInSeconds, yDrag, yVelocity, yAcceleration);
                YVelocity = updatedYVelocity;
            }

            double updatedRotationVelocity = rotationVelocity;
            if (rotationAcceleration != 0 || (rotationDrag != 0 && rotationVelocity != 0))
            {
                updatedRotationVelocity = PhysicsUtils.GetNewVelocity(elapsedTimeInSeconds, rotationDrag, rotationVelocity, rotationAcceleration);
                RotationVelocity = updatedRotationVelocity;
            }

            double updatedScaleXVelocity = scaleXVelocity;
            if (scaleXAcceleration != 0 || (scaleXDrag != 0 && scaleXVelocity != 0))
            {
                updatedScaleXVelocity = PhysicsUtils.GetNewVelocity(elapsedTimeInSeconds, scaleXDrag, scaleXVelocity, scaleXAcceleration);
                ScaleXVelocity = updatedScaleXVelocity;
            }

            double updatedScaleYVelocity = scaleYVelocity;
            if (scaleYAcceleration != 0 || (scaleYDrag != 0 && scaleYVelocity != 0))
            {
                updatedScaleYVelocity = PhysicsUtils.GetNewVelocity(elapsedTimeInSeconds, scaleYDrag, scaleYVelocity, scaleYAcceleration);
                ScaleYVelocity = updatedScaleYVelocity;
            }

            // most of these params are unnecessary unless we want to do the integral.
            if (updatedXVelocity != 0.0)
            {
                X = PhysicsUtils.GetNewPosition(elapsedTimeInSeconds, xDrag, X, xAcceleration, updatedXVelocity, xVelocity);
            }
            if (updatedYVelocity != 0.0)
            {
                Y = PhysicsUtils.GetNewPosition(elapsedTimeInSeconds, yDrag, Y, yAcceleration, updatedYVelocity, yVelocity);
            }
            if (updatedRotationVelocity != 0.0)
            {
                Rotation = PhysicsUtils.GetNewPosition(elapsedTimeInSeconds, rotationDrag, Rotation, rotationAcceleration, updatedRotationVelocity, rotationVelocity);
            }
            if (updatedScaleXVelocity != 0.0)
            {
                ScaleX = PhysicsUtils.GetNewPosition(elapsedTimeInSeconds, scaleXDrag, ScaleX, scaleYAcceleration, updatedScaleXVelocity, scaleXVelocity);
            }
            if (updatedYVelocity != 0.0)
            {
                ScaleY = PhysicsUtils.GetNewPosition(elapsedTimeInSeconds, scaleYDrag, ScaleY, scaleYAcceleration, updatedScaleYVelocity, scaleYVelocity);
            }

            // Zero out acceleration so all forces actually have to act every frame
            XAcceleration = 0;
            YAcceleration = 0;
            RotationAcceleration = 0;
            ScaleXAcceleration = 0;
            ScaleYAcceleration = 0;
        }

        private void SilverlightUpdate()
        {            
            if (VisualWidth != PrevVisualWidth)
            {
                try
                {
                    m_actorCanvas.__Scale.ScaleX = ScaleX;
                }
                catch (Exception e)
                {
                    Console.ReportError(
                        "Error setting ScaleX of actor '" + Name + "' - Silverlight will not accept a value of '" +
                        ScaleX + "'.\n" +
                        "Message: " + e.Message + "\n" +
                        "Exception: " + e.ToString() + "\n");
                }

                double centerX = -m_currentState.VisualXOffset * ScaleX + Width / 2.0;
                try
                {
                    
                    m_actorCanvas.__Rotate.CenterX = centerX;
                }
                catch (Exception e)
                {
                    Console.ReportError(
                        "Error setting rotation CenterX of actor '" + Name + "' - Silverlight will not accept a value of '" +
                        centerX + "'.\n" +
                        "Message: " + e.Message + "\n" +
                        "Exception: " + e.ToString() + "\n");
                }
            }
            if (VisualHeight != PrevVisualHeight)
            {
                try 
                {
                    m_actorCanvas.__Scale.ScaleY = ScaleY;
                }
                catch (Exception e)
                {
                    Console.ReportError(
                        "Error setting ScaleY of actor '" + Name + "' - Silverlight will not accept a value of '" +
                        ScaleY + "'.\n" +
                        "Message: " + e.Message + "\n" +
                        "Exception: " + e.ToString() + "\n");
                }
                double centerY = -m_currentState.VisualYOffset * ScaleY + Height / 2.0;
                try
                {
                    m_actorCanvas.__Rotate.CenterY = centerY;
                }
                catch (Exception e)
                {
                    Console.ReportError(
                        "Error setting rotation CenterY of actor '" + Name + "' - Silverlight will not accept a value of '" +
                        centerY + "'.\n" +
                        "Message: " + e.Message + "\n" +
                        "Exception: " + e.ToString() + "\n");
                }
            }

            if (PrevOpacity != Opacity)
            {
                try
                {
                    m_actorCanvas.Opacity = Opacity;
                }
                catch (Exception e)
                {
                    Console.ReportError(
                        "Error setting Opacity of actor '" + Name + "' - Silverlight will not accept a value of '" +
                        Opacity + "'.\n" +
                        "Message: " + e.Message + "\n" +
                        "Exception: " + e.ToString() + "\n");
                }
            }

            if (VisualX != PrevVisualX)
            {
                try
                {
                    System.Windows.Controls.Canvas.SetLeft(m_actorCanvas, VisualX);
                }
                catch (Exception e)
                {
                    Console.ReportError(
                        "Error setting VisualX position of actor '" + Name + "' - Silverlight will not accept a value of '" +
                        VisualX + "'.\n" +
                        "Message: " + e.Message + "\n" +
                        "Exception: " + e.ToString() + "\n");
                }
            }
            if (VisualY != PrevVisualY)
            {
                try 
                {
                    System.Windows.Controls.Canvas.SetTop(m_actorCanvas, VisualY);
                }
                catch (Exception e)
                {
                    Console.ReportError(
                        "Error setting VisualY position of actor '" + Name + "' - Silverlight will not accept a value of '" +
                        VisualY + "'.\n" +
                        "Message: " + e.Message + "\n" +
                        "Exception: " + e.ToString() + "\n");
                }
            }

            if (PrevZIndex != ZIndex)
            {
                try 
                {
                    System.Windows.Controls.Canvas.SetZIndex(m_actorCanvas, ZIndex);
                }
                catch (Exception e)
                {
                    Console.ReportError(
                        "Error setting ZIndex of actor '" + Name + "' - Silverlight will not accept a value of '" +
                        ZIndex + "'.\n" +
                        "Message: " + e.Message + "\n" +
                        "Exception: " + e.ToString() + "\n");
                }
            }

            if (Rotation != PrevRotation)
            {
                try 
                {
                    m_actorCanvas.__Rotate.Angle = Rotation;
                }
                catch (Exception e)
                {
                    Console.ReportError(
                        "Error setting Rotation of actor '" + Name + "' - Silverlight will not accept a value of '" +
                        Rotation + "'.\n" +
                        "Message: " + e.Message + "\n" +
                        "Exception: " + e.ToString() + "\n");
                }
            }


            PrevHeight = Height;
            PrevOpacity = Opacity;
            PrevRotation = Rotation;
            PrevVisualHeight = VisualHeight;
            PrevVisualWidth = Width;
            PrevVisualX = VisualX;
            PrevVisualY = VisualY;
            PrevWidth = Width;
            PrevX = X;
            PrevY = Y;
            PrevZIndex = ZIndex;
        }

        public Collisions.CollisionBounds GetVisualBounds()
        {
            return new Collisions.CollisionBounds(VisualX, VisualY, VisualWidth, VisualHeight, CollisionEdges.EdgesType.RECT);
        }

        internal Events.CollisionDirectionArg MostRecentSceneOverlapDirection { get; private set; }
        public bool IsOffScene()
        {
            Events.CollisionDirectionArg direction = Collisions.CollisionMath.OverlapRectRect(GetBounds(), new Collisions.CollisionBounds(0, 0, Scene.Width, Scene.Height, CollisionEdges.EdgesType.RECT), this, Scene);

            if (direction != null)
            {
                MostRecentSceneOverlapDirection = direction;
            }

            return (direction == null);
        }

        internal Events.CollisionDirectionArg MostRecentVisualSceneOverlapDirection { get; private set; }
        public bool IsVisualOffScene()
        {
            Events.CollisionDirectionArg direction = Collisions.CollisionMath.OverlapRectRect(GetVisualBounds(), new Collisions.CollisionBounds(0, 0, Scene.Width, Scene.Height, CollisionEdges.EdgesType.RECT), this, Scene);

            if (direction != null)
            {
                MostRecentVisualSceneOverlapDirection = direction;
            }

            return (direction == null);
        }

        internal Events.CollisionDirectionArg MostRecentViewportOverlapDirection { get; private set; }
        public bool IsOffViewport()
        {
            Events.CollisionDirectionArg direction = Collisions.CollisionMath.OverlapRectRect(GetBounds(), new Collisions.CollisionBounds(Scene.ViewportX, Scene.ViewportY, Scene.ViewportWidth, Scene.ViewportHeight, CollisionEdges.EdgesType.RECT), this, Scene);

            if (direction != null)
            {
                MostRecentViewportOverlapDirection = direction;
            }

            return (direction == null);
        }

        internal Events.CollisionDirectionArg MostRecentVisualViewportOverlapDirection { get; private set; }
        public bool IsVisualOffViewport()
        {
            Events.CollisionDirectionArg direction = Collisions.CollisionMath.OverlapRectRect(GetVisualBounds(), new Collisions.CollisionBounds(Scene.ViewportX, Scene.ViewportY, Scene.ViewportWidth, Scene.ViewportHeight, CollisionEdges.EdgesType.RECT), this, Scene);

            if (direction != null)
            {
                MostRecentVisualViewportOverlapDirection = direction;
            }

            return (direction == null);
        }

        public Point GetPosition()
        {
            return new Point(X, Y);
        }

        public Collisions.CollisionBounds GetBounds()
        {
            return new Collisions.CollisionBounds(X, Y, Width, Height, CurrentState.Edges.Type);
        }

        public override System.Windows.Controls.Canvas GetCanvasRoot()
        {
            return m_actorCanvas;
        }
        public System.Windows.Controls.Canvas GetVisualRoot()
        {
            return (System.Windows.Controls.Canvas) m_actorCanvas.Children[0];
        }
        public void Remove()
        {
            Remove(default(Data.EffectInfo));
        }
       
        /// <summary>
        /// Not strictly necessary anymore for built in properties, but this holdover from the 
        /// jscript engine allows properties to be set from custom code.  For built in properties 
        /// (those listed in the switch statement), it's probably better to just set them directly.
        /// For user defined properties, SetValue and GetValue still have some use
        /// </summary>
        /// <param name="propertyName">Property to set</param>
        /// <param name="value">value to set propertyName to</param>
        public override void SetValue(String propertyName, Object value)
		{
            try
            {
                // handle the built in properties
                switch (propertyName)
                {
                    case "X":
                        X = Utils.ValidDoubleFromObject(value);
                        break;
                    case "Y":
                        Y = Utils.ValidDoubleFromObject(value);
                        break;
                    case "VisualX":
                        VisualX = Utils.ValidDoubleFromObject(value);
                        break;
                    case "VisualY":
                        VisualY = Utils.ValidDoubleFromObject(value);
                        break;
                    case "XVelocity":
                        XVelocity = Utils.ValidDoubleFromObject(value);
                        break;
                    case "YVelocity":
                        YVelocity = Utils.ValidDoubleFromObject(value);
                        break;
                    case "XAcceleration":
                        XAcceleration = Utils.ValidDoubleFromObject(value);
                        break;
                    case "YAcceleration":
                        YAcceleration = Utils.ValidDoubleFromObject(value);
                        break;
                    case "XDrag":
                        XDrag = Utils.ValidDoubleFromObject(value);
                        break;
                    case "YDrag":
                        YDrag = Utils.ValidDoubleFromObject(value);
                        break;
                    case "Rotation":
                        Rotation = Utils.ValidDoubleFromObject(value);
                        break;
                    case "RotationVelocity":
                        RotationVelocity = Utils.ValidDoubleFromObject(value);
                        break;
                    case "RotationAcceleration":
                        RotationAcceleration = Utils.ValidDoubleFromObject(value);
                        break;
                    case "RotationDrag":
                        RotationDrag = Utils.ValidDoubleFromObject(value);
                        break;
                    case "Width":
                        Width = Utils.ValidDoubleFromObject(value);
                        break;
                    case "Height":
                        Height = Utils.ValidDoubleFromObject(value);
                        break;
                    case "ScaleX":
                        ScaleX = Utils.ValidDoubleFromObject(value);
                        break;
                    case "ScaleY":
                        ScaleY = Utils.ValidDoubleFromObject(value);
                        break;
                    case "ScaleXVelocity":
                        ScaleXVelocity = Utils.ValidDoubleFromObject(value);
                        break;
                    case "ScaleYVelocity":
                        ScaleYVelocity = Utils.ValidDoubleFromObject(value);
                        break;
                    case "ScaleXAcceleration":
                        ScaleXAcceleration = Utils.ValidDoubleFromObject(value);
                        break;
                    case "ScaleYAcceleration":
                        ScaleYAcceleration = Utils.ValidDoubleFromObject(value);
                        break;
                    case "ScaleXDrag":
                        ScaleXDrag = Utils.ValidDoubleFromObject(value);
                        break;
                    case "ScaleYDrag":
                        ScaleYDrag = Utils.ValidDoubleFromObject(value);
                        break;
                    case "Opacity":
                        Opacity = Utils.ValidDoubleFromObject(value);
                        break;
                    case "ZIndex":
                        ZIndex = Utils.IntFromObject(value);
                        break;
                    case "Mass":
                        Mass = Utils.ValidDoubleFromObject(value);
                        break;
                    default:
                        // Not a built in property so set it using PropertyHolder
                        base.SetValue(propertyName, value);
                        break;
                }
            }
            catch (Exception e)
            {
                Console.ReportError(
                    "Error setting property '" + propertyName + "' to value '" + value.ToString() + " on actor '" + Name + "'.\n" +
                    "Message: " + e.Message + "\n" +
                    "Exception: " + e.ToString() + "\n");
            }
        }

        /// <summary>
        /// See SetValue for more info
        /// </summary>
        /// <param name="propertyName">Property whose value to retrieve</param>
        /// <returns>The value of the property described by propertyName</returns>
        public override Object GetValue(String propertyName)
        {
            switch (propertyName)
            {
                case "X":
                    return X;
                case "Y":
                    return Y;
                case "VisualX":
                    return VisualX;
                case "VisualY":
                    return VisualY;
                case "XVelocity":
                    return XVelocity;
                case "YVelocity":
                    return YVelocity;
                case "XAcceleration":
                    return XAcceleration;
                case "YAcceleration":
                    return YAcceleration;
                case "XDrag":
                    return XDrag;
                case "YDrag":
                    return YDrag;
                case "Rotation":
                    return Rotation;
                case "RotationVelocity":
                    return RotationVelocity;
                case "RotationAcceleration":
                    return RotationAcceleration;
                case "RotationDrag":
                    return RotationDrag;
                case "Width":
                    return Width;
                case "Height":
                    return Height;
                case "ScaleX":
                    return ScaleX;
                case "ScaleY":
                    return ScaleY;
                case "ScaleXVelocity":
                    return ScaleXVelocity;
                case "ScaleYVelocity":
                    return ScaleYVelocity;
                case "ScaleXAcceleration":
                    return ScaleXAcceleration;
                case "ScaleYAcceleration":
                    return ScaleYAcceleration;
                case "ScaleXDrag":
                    return ScaleXDrag;
                case "ScaleYDrag":
                    return ScaleYDrag;
                case "Opacity":
                    return Opacity;
                case "ZIndex":
                    return ZIndex;
                case "Mass":
                    return Mass;
                case "TypeName":
                    return ActorName;
                default:
                    return base.GetValue(propertyName);
            } // end switch
        } // end method
        internal bool FindInPushedList(Actor actorToFind)
        {
            if (PushingActor == null)
            {
                return false;
            }
            else if (PushingActor == actorToFind)
            {
                return true;
            }
            else
            {
                return PushingActor.FindInPushedList(actorToFind);
            }
        } // end FindInPushedList method

        private bool m_decremented = false;
        internal void DecrementCount()
        {
            if (m_decremented == false) // only do this once, even if they try to remove us multiple times.
            {
                string numActorProp = "# of " + ActorName;
                m_scene.SetValue(numActorProp, Utils.IntFromObject(m_scene.GetValue(numActorProp)) - 1);
            }
            m_decremented = true;
        }
    }// end actor class
} // end namespace
